﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Import.Exceptions;

namespace Import
{
    public class TimeCode : IEquatable<TimeCode>
    {
        #region Enums

        public enum SourceValueType
        {
            Unknown = 0,
            Frames = 1,
            Seconds = 2,
            Minutes = 3,
            Hours = 4
        }

        #endregion

        #region Constructors

        public TimeCode()
        {
            this.Hour   = "00";
            this.Minute = "00";
            this.Second = "00";
            this.Frame  = "00";
        }
        public TimeCode(string hour, string minute, string second, string frame)
        {
            this.Hour   = hour;
            this.Minute = minute;
            this.Second = second;
            this.Frame  = frame;

            this.AddPadding();
        }
        public TimeCode(string timeCode, SourceValueType sourceValueType = SourceValueType.Unknown, bool acceptEmptyValue = false)
        {
            this.Hour   = "00";
            this.Minute = "00";
            this.Second = "00";
            this.Frame  = "00";

            if ( TimeCode.IsEmpty(timeCode) )
            {
                if (acceptEmptyValue)
                    return;
                throw new MissingTimeCodeException(null);
            }

            // TODO => Figure out if there's a better way to do this

            // Format:
            // ===============================================================================
            // HH = Hours, MM => Minutes, SS => Seconds, FF => Frames
                
            // Possible scenarios:
            // ===============================================================================
            // #1) HH:MM:SS:FF      =>  10:00:14:05 => [0] = 10, [1] = 00, [2] = 14, [3] = 05
            // #2) HH:MM:SS         =>  10:00:14    => [0] = 10, [1] = 00, [2] = 14
            // #3) MM:SS            =>  00:14       => [0] = 00, [1] = 14
            // #4) SS               =>  14          => [0] = 14

            var timeCodeParts = timeCode.RemoveWhitespaces().Split(':');

            if ( !TimeCode.ValidateTimeCodeParts(timeCodeParts) )
                throw new InvalidFormatTimeCodeException(null);

            switch (sourceValueType)
            {
                case SourceValueType.Unknown:
                    #region Unknown
                    if (timeCodeParts.Length == 1)
                    {
                        //this.Hour = "00";
                        //this.Minute = "00";
                        this.Second = timeCodeParts[0];
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 2)
                    {
                        //this.Hour = "00";
                        this.Minute = timeCodeParts[0];
                        this.Second = timeCodeParts[1];
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 3)
                    {
                        this.Hour = timeCodeParts[0];
                        this.Minute = timeCodeParts[1];
                        this.Second = timeCodeParts[2];
                        //this.Frame = "00";
                    }
                    #endregion
                    break;
                case SourceValueType.Frames:
                    #region Frames
                    if (timeCodeParts.Length == 1)
                    {
                        //this.Hour   = "00";
                        //this.Minute = "00";
                        //this.Second = "00";
                        this.Frame = timeCodeParts[0];
                    }
                    if (timeCodeParts.Length == 2)
                    {
                        //this.Hour   = "00";
                        //this.Minute = "00";
                        this.Second = timeCodeParts[0];
                        this.Frame = timeCodeParts[1];
                    }
                    if (timeCodeParts.Length == 3)
                    {
                        //this.Hour   = "00";
                        this.Minute = timeCodeParts[0];
                        this.Second = timeCodeParts[1];
                        this.Frame = timeCodeParts[2];
                    }
                    #endregion
                    break;
                case SourceValueType.Seconds:
                    #region Seconds
                    if (timeCodeParts.Length == 1)
                    {
                        //this.Hour   = "00";
                        //this.Minute = "00";
                        this.Second = timeCodeParts[0];
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 2)
                    {
                        //this.Hour   = "00";
                        this.Minute = timeCodeParts[0];
                        this.Second = timeCodeParts[1];
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 3)
                    {
                        this.Hour   = timeCodeParts[0];
                        this.Minute = timeCodeParts[1];
                        this.Second = timeCodeParts[2];
                        //this.Frame = "00";
                    }
                    #endregion
                    break;
                case SourceValueType.Minutes:
                    #region Minutes
                    if (timeCodeParts.Length == 1)
                    {
                        //this.Hour   = "00";
                        this.Minute = timeCodeParts[0];
                        //this.Second = "00";
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 2)
                    {
                        //this.Hour   = "00";
                        this.Minute = timeCodeParts[0];
                        this.Second = timeCodeParts[1];
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 3)
                    {
                        this.Hour   = timeCodeParts[0];
                        this.Minute = timeCodeParts[1];
                        this.Second = timeCodeParts[2];
                        //this.Frame = "00";
                    }
                    #endregion
                    break;
                case SourceValueType.Hours:
                    #region Hours
                    if (timeCodeParts.Length == 1)
                    {
                        this.Hour   = timeCodeParts[0];
                        //this.Minute = "00";
                        //this.Second = "00";
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 2)
                    {
                        this.Hour   = timeCodeParts[0];
                        this.Minute = timeCodeParts[1];
                        //this.Second = "00";
                        //this.Frame = "00";
                    }
                    if (timeCodeParts.Length == 3)
                    {
                        this.Hour   = timeCodeParts[0];
                        this.Minute = timeCodeParts[1];
                        this.Second = timeCodeParts[2];
                        //this.Frame = "00";
                    }
                    #endregion
                    break;
            }

            if (timeCodeParts.Length == 4)
            {
                this.Hour = timeCodeParts[0];
                this.Minute = timeCodeParts[1];
                this.Second = timeCodeParts[2];
                this.Frame = timeCodeParts[3];                            
            }

            this.AddPadding();
        }

        #endregion

        #region Properties

        public string Hour { get; set; }
        public string Minute { get; set; }
        public string Second { get; set; }
        public string Frame { get; set; }

        #endregion

        #region Public Methods

        #region Overriden methods (for comparison purposes)
        public override int GetHashCode()
        {
            return int.Parse(this.Hour) + int.Parse(this.Minute) + int.Parse(this.Second) + int.Parse(this.Frame);
        }
        public override bool Equals(object obj)
        {
            return Equals(obj as TimeCode);
        }
        public bool Equals(TimeCode timeCode)
        {
            return AreEqual(this, timeCode);
        }
        #endregion

        public override string ToString()
        {
            this.AddPadding();
            return String.Format( "{0}:{1}:{2}", this.Hour.IsNotNull() ? this.Hour : "00", this.Minute.IsNotNull() ? this.Minute : "00", this.Second.IsNotNull() ? this.Second : "00" );
        }
        public string ToString(string timeCodeFormat)
        {
            if (timeCodeFormat == null)
                return this.ToString();

            this.AddPadding();

            var formattedTimeCode = String.Empty;
            switch (timeCodeFormat.ToLower())
            {
                case "hhmmss":
                    formattedTimeCode = String.Format( "{0}{1}{2}", this.Hour.IsNotNull() ? this.Hour : "00", this.Minute.IsNotNull() ? this.Minute : "00", this.Second.IsNotNull() ? this.Second : "00" );
                    break;
                case "hh:mm:ss":
                    formattedTimeCode = String.Format( "{0}:{1}:{2}", this.Hour.IsNotNull() ? this.Hour : "00", this.Minute.IsNotNull() ? this.Minute : "00", this.Second.IsNotNull() ? this.Second : "00" );
                    break;
                case "mm:ss":
                    formattedTimeCode = String.Format( "{0}:{1}", this.Minute.IsNotNull() ? this.Minute : "00", this.Second.IsNotNull() ? this.Second : "00" );
                    break;
                case "ss":
                    formattedTimeCode = String.Format("{0}", this.Second.IsNotNull() ? this.Second : "00");
                    break;
            }

            return formattedTimeCode;
        }

        public static void SetHour(TimeCode timeCode, int hour)
        {
            timeCode.Hour = hour.ToString();
        }

        public static string GetCorrespondingFormat(List<TimeCode> timeCodes)
        {
            var format = "hh:mm:ss";
            var lastTimeCode = timeCodes.LastOrDefault();
            if (lastTimeCode != null)
            {
                if (Convert.ToInt32(lastTimeCode.Hour) == 0)
                    format = "mm:ss";
                if (Convert.ToInt32(lastTimeCode.Minute) == 0)
                    format = "ss";
            }
            return format;
        }

        public bool IsGreaterOrEqualThan(TimeCode timeCode)
        {
            return IsGreaterOrEqualThan(this, timeCode);
        }
        public static bool IsGreaterOrEqualThan(TimeCode timeCode1, TimeCode timeCode2)
        {
            var t1 = Convert.ToInt32( timeCode1.ToString("hhmmss") );
            var t2 = Convert.ToInt32( timeCode2.ToString("hhmmss") );
            return t1 >= t2;
        }

        public static bool AreEqual(TimeCode t1, TimeCode t2)
        {
            if ( t1 == null && t2 == null )
                return true;

            if ( (t1 != null && t2 == null) || (t1 == null && t2 != null) )
                return false;
            
            if (t1.Hour == null && t2.Hour == "00")
                t1.Hour = "00";
            if (t1.Hour == "00" && t2.Hour == null)
                t2.Hour = "00";
            return t1.Hour == t2.Hour && t1.Minute == t2.Minute && t1.Second == t2.Second;
        }

        public static bool IsEmpty(string timeCode)
        {
            return timeCode.RemoveWhitespaces().Length == 0;
        }

        public static bool ValidateTimeCodeParts(string[] timeCodeParts)
        {
            var eachSegmentHasBetweenOneAndTwoDigits = timeCodeParts.All(x => x.Length >= 1 && x.Length <= 4);
            var allSegmentsAreNumbers = true;
            foreach (var part in timeCodeParts)
            {
                var number = -1;
                if (!Int32.TryParse(part, out number))
                {
                    allSegmentsAreNumbers = false;
                    break;
                }
            }

            return allSegmentsAreNumbers && eachSegmentHasBetweenOneAndTwoDigits;
        }

        public static void Sort(params TimeCode[] timeCodes)
        {
            for (var i = 0; i < timeCodes.Length; i++)
            {
                for (var sort = 0; sort < timeCodes.Length - 1; sort++)
                {
                    if ( TimeCode.IsGreaterOrEqualThan( timeCodes[sort] , timeCodes[sort + 1] ) )
                    {
                        var temp = timeCodes[sort + 1];
                        timeCodes[sort + 1] = timeCodes[sort];
                        timeCodes[sort] = temp;
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        private void AddPadding()
        {
            this.Hour   = this.Pad(this.Hour);
            this.Minute = this.Pad(this.Minute);
            this.Second = this.Pad(this.Second);
            this.Frame  = this.Pad(this.Frame);
        }

        private string Pad(string value, char chr = '0')
        {
            return value.PadLeft(2, chr);
        }

        #endregion
    }
}
